package com.wh1200.mybatis.xmlless.resolver

import com.wh1200.kotlin.toWords
import com.wh1200.mybatis.xmlless.annotations.TestCriteria
import com.wh1200.mybatis.xmlless.annotations.XmllessMethod
import com.wh1200.mybatis.xmlless.config.BaseResolverTest
import com.wh1200.mybatis.xmlless.enums.XmllessDbType
import com.wh1200.mybatis.xmlless.model.QueryCriteriaGroup
import com.wh1200.xmlless.test.dao.StudentDAO
import org.apache.ibatis.annotations.Param
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import java.lang.reflect.Method
import kotlin.reflect.jvm.javaMethod
import kotlin.test.assertContains
import kotlin.test.assertEquals
import kotlin.test.assertNotNull

/**
 *
 * @author 吴昊
 * @since 0.0.8
 */
@Suppress("unused")
interface TestDAO {

  /**
   */
  @XmllessMethod(
      conditions = [
        "name = 0"
      ]
  )
  fun findByNameEq0()

  /**
   * @param min
   * @param max
   */
  fun findByAgeBetweenMinAndMax(
      @Param("min") min: Int,
      @Param("max") max: Int
  )

  /**
   * @param minAge
   * @param maxAge
   */
  fun findByAgeBetween(@Param("minAge") minAge: Int, @Param("maxAge") maxAge: Int)

  /**
   */
  @XmllessMethod(
      conditions = [
        "name = '张三'"
      ]
  )
  fun findZhangSan()

  /**
   * @param keywords
   */
  fun findByNameLikeKeywords(keywords: String)

  /**
   * @param form
   */
  fun findByNameAndAge(form: Form)

  /**
   * @param form
   */
  @XmllessMethod(conditions = ["name", "age"])
  fun findByNameAndAge3(form: Form2)

  /**
   * @param fm
   */
  @XmllessMethod(conditions = ["name", "age"])
  fun findByNameAndAge2(@Param("f") fm: Form)

}

/**
 * 测试条件解析
 *
 * @author 吴昊
 * @since 0.0.8
 */
open class ConditionResolverTest : BaseResolverTest(StudentDAO::class.java) {

  @Test
  fun toWords() {
    assertEquals(listOf("A", "Eq", "0"), "aEq0".toWords())
    assertEquals(listOf("A", "Eq", "B"), "aEqB".toWords())
    assertEquals(listOf("UserName", "Eq", "B"), "UserName Eq B".toWords())
  }

  @Test
  fun resolveNamedComplexParam() {
    val method = TestDAO::findByNameAndAge2.javaMethod!!
    val exp = "name and age"
    val conditions = resolveConditions(exp, method)
    assertEquals(2, conditions.criterion.size)
    assertEquals("name = #{f.name}", conditions.criterion[0].toString())
    assertEquals("age = #{f.age}", conditions.criterion[1].toString())
  }

  @Test
  fun resolveComplexParam() {
    val method = TestDAO::findByNameAndAge.javaMethod!!
    val exp = method.name.replace("findBy", "")
    assertEquals("NameAndAge", exp)
    val conditions = resolveConditions(exp, method)
    assertEquals(2, conditions.criterion.size)
    assertEquals("name = #{name}", conditions.criterion[0].toString())
    assertEquals("age = #{age}", conditions.criterion[1].toString())
  }

  @Test
  fun resolveComplexParam9() {
    val method = TestDAO::findByNameAndAge3.javaMethod!!
    val exp = "NameAndAge"
    assertEquals("NameAndAge", exp)
    val conditions = resolveConditions(exp, method)
    assertEquals(2, conditions.criterion.size)
    assertEquals("name = #{name}", conditions.criterion[0].toString())
    assertEquals("age = #{age}", conditions.criterion[1].toString())
  }

  @Test
  fun resolveComplexParam2() {
    val method = StudentDAO::findByNameLikeAndAgeAndCreateTimeBetweenStartAndEnd.javaMethod!!
    val exp = method.name.replace("findBy", "")
    val conditions = resolveConditions(exp, method)
    assertEquals(3, conditions.criterion.size)
    assertEquals("name LIKE CONCAT('%', #{name},'%')", conditions.criterion[0].toString())
    assertEquals("age = #{age}", conditions.criterion[1].toString())
    assertEquals("createTime BETWEEN #{start} AND #{end}", conditions.criterion[2].toString())
  }

  @Test
  fun resolveComplexParam3() {
    val method = StudentDAO::findByNameLikeAndAgeAndCreateTimeBetweenStartAndEndPageable.javaMethod!!
    val exp = "NameLikeAndAgeAndCreateTimeBetweenStartAndEnd"
    val conditions = resolveConditions(exp, method)
    assertEquals(3, conditions.criterion.size)
    assertEquals("name LIKE CONCAT('%', #{form.name},'%')", conditions.criterion[0].toString())
    assertEquals("age = #{form.age}", conditions.criterion[1].toString())
    assertEquals("createTime BETWEEN #{form.start} AND #{form.end}", conditions.criterion[2].toString())
  }

  @Test
  @DisplayName("条件表达式操作符后面省略参数名称")
  fun noParamNameAfterOperator() {
    val method = StudentDAO::findByNameLikeAndAgeAndCreateTimeBetweenStartAndEndPageable3.javaMethod!!
    val q = createQueryForMethod(method)
    assertNotNull(q)
  }

  @Test
  @DisplayName("between表达式")
  fun between() {
    val method = StudentDAO::findByBirthdayBetween.javaMethod!!
    val method2 = StudentDAO::findByBirthdayBetween2.javaMethod!!
    val q = createQueryForMethod(method)
    val q2 = createQueryForMethod(method2)
    assertNotNull(q.sql)
    assertNotNull(q2.sql)
  }

  @Test
  @DisplayName("not in表达式")
  fun notIn() {
    val method = StudentDAO::findByIdNotIn.javaMethod!!
    val q = createQueryForMethod(method)
    q.exception?.printStackTrace()
    val sql = q.sql
    assertNotNull(sql)
    if (dbType == XmllessDbType.MYSQL) {
      assertContains(sql, "JSON_CONTAINS(")
      assertContains(sql, ", '\$') AND")
    } else if (dbType == XmllessDbType.POSTGRE_SQL) {
      assertContains(sql, "(t_student.favorites)::jsonb")
    }
    println(q.sql)
  }

  @Test
  @DisplayName("条件分组")
  fun conditionGroup() {
    val method = StudentDAO::find999.javaMethod!!
    val q = createQueryForMethod(method)
    q.exception?.printStackTrace()
    assertNotNull(q.sql)
  }


  @Test
  fun conditionMixWithoutParam() {
    val method = StudentDAO::find19.javaMethod!!
    val q = createQueryForMethod(method)
    q.exception?.printStackTrace()
    assertNotNull(q.sql)
    println(q.sql)
  }

  @Test
  @DisplayName("动态字段查询")
  fun dynamicColumn() {
    val method = StudentDAO::updateByValue.javaMethod!!
    val q = createQueryForMethod(method)
    q.exception?.printStackTrace()
    val sql = q.sql
    assertNotNull(sql)
    assertContains(sql, "detail = JSON_SET(detail, '\$.\${path}'")
    assertContains(sql, "\${path} = #{value}")
  }

  @Test
  fun markGraduated() {
    val method = StudentDAO::markGraduated.javaMethod!!
    val q = createQueryForMethod(method)
    q.exception?.printStackTrace()
    assertNotNull(q.sql)
    println(q.sql)
  }

  @Test
  fun resolveComplexParam4() {
    val method = StudentDAO::findByNameLikeAndAgeAndCreateTimeBetweenStartAndEndPageable3.javaMethod!!
    val exp = "NameLikeAndAgeAndCreateTimeBetweenStartAndEnd"
    val conditions = resolveConditions(exp, method)
    assertEquals(3, conditions.criterion.size)
    assertEquals("name LIKE CONCAT('%', #{form.name},'%')", conditions.criterion[0].toString())
    assertEquals("age = #{form.age}", conditions.criterion[1].toString())
    assertEquals("createTime BETWEEN #{form.start} AND #{form.end}", conditions.criterion[2].toString())
  }

  @Test
  fun specificValue() {
    val method = TestDAO::findZhangSan.javaMethod!!
    val q = createQueryForMethod(method)
    q.exception?.printStackTrace()
    val sql = q.sql
    assertNotNull(sql)
    assertContains(sql, "t_student.name = '张三'")
  }

  @Test
  fun resolveBetweenWithoutParamName() {
    val method = TestDAO::findByAgeBetween.javaMethod!!
    val exp = method.name.replace("findBy", "")
    assertEquals("AgeBetween", exp)
    val conditions = resolveConditions(exp, method)
    assertEquals(1, conditions.criterion.size)
    assertEquals("age BETWEEN #{minAge} AND #{maxAge}", conditions.criterion.first().toString())
  }

  @Test
  fun resolveBetween() {
    val method = TestDAO::findByAgeBetweenMinAndMax.javaMethod!!
    val exp = method.name.replace("findBy", "")
    assertEquals("AgeBetweenMinAndMax", exp)
    val conditions = resolveConditions(exp, method)
    assertEquals(1, conditions.criterion.size)
    assertEquals("age BETWEEN #{min} AND #{max}", conditions.criterion.first().toString())
  }

  @Test
  fun resolveEq0() {
    val method = TestDAO::findByNameEq0.javaMethod!!
    val conditions = resolveConditions("nameEq0", method)
    assertEquals(1, conditions.criterion.size)
    assertEquals("name = 0", conditions.criterion.first().toString())
  }

  private fun resolveConditions(
      conditionExpression: String,
      method: Method
  ): QueryCriteriaGroup {
    super.mappings
    val criteria = CriteriaResolver.resolveConditions(
        conditionExpression, createMethodInfo(method)
    )
    if (criteria is QueryCriteriaGroup) {
      return criteria
    } else {
      return QueryCriteriaGroup(listOf(criteria), createMethodInfo(method))
    }
  }

}

open class Form {

  var name: String? = null
  var age: Int? = null

}

class Form2 : Form() {

  @TestCriteria(
      ifExpression = "= true",
      sqlExpression = "1 = 2"
  )
  var active: Boolean = false

}
